/**
 * Project:   dtu.fmes.googleloon
 * File:      WindSimulationLink.java
 * License: 
 *            This file is licensed under GNU General Public License version 3
 *            http://www.gnu.org/licenses/gpl-3.0.txt
 *
 * Copyright: Bartosz Cichecki [ cichecki.bartosz@gmail.com ]
 * Date:      Nov 7, 2013
 */

package dtu.fmes.loon.simulation.processors;

import dtu.fmes.loon.model.Balloon;
import dtu.fmes.loon.model.Vector;
import dtu.fmes.loon.model.environment.Wind;

/**
 * This simulation link simulates the wind that pushes balloons around.
 * 
 * @author Bartosz Cichecki, Nicolai Sonne
 * 
 */
public class WindSimulationLink extends SimulationLink {

	/**
	 * Get wind by combining two closest wind. The closer the balloon is toward
	 * one wind, the more it affects it
	 * 
	 * @param wind1
	 * @param wind2
	 * @param altitude
	 * @return
	 */
	private Wind getWindGradient(Wind wind1, Wind wind2, Double altitude) {
		Double totalHeight = Math.abs(wind1.getHeight() - wind2.getHeight());
		Double wind1fraction = Math.abs(wind1.getHeight() - altitude) / totalHeight;
		Double wind2fraction = Math.abs(wind2.getHeight() - altitude) / totalHeight;

		Double x = wind1.getVector().getX() * wind1fraction + wind2.getVector().getX() * wind2fraction;
		Double y = wind1.getVector().getY() * wind1fraction + wind2.getVector().getY() * wind2fraction;

		return new Wind(null, new Vector(x, y));
	}

	private Wind getWind(Double altitude, Wind[] winds) {

		Wind closestWind = null;
		Wind secondClosestWind = null;

		// Find closest wind
		Double distance = null;
		for (Wind currentWind : winds) {
			Double currentDistance = Math.abs(currentWind.getHeight() - altitude);

			if (distance == null || currentDistance < distance) {
				distance = currentDistance;
				closestWind = currentWind;
			}
		}

		// Return closest wind if balloon is directly placed in it
		if (closestWind.getHeight().equals(altitude)) return closestWind;

		if (closestWind.getHeight() > altitude) {
			// Closest wind is above balloon
			// Find closest wind below balloon
			distance = null;
			for (Wind currentWind : winds) {
				if (currentWind.getHeight() < altitude) {
					Double currentDistance = altitude - currentWind.getHeight();
					if (distance == null || currentDistance < distance) {
						distance = currentDistance;
						secondClosestWind = currentWind;
					}
				}
			}

			if (secondClosestWind != null) {
				// Wind below found. Return wind gradient
				return getWindGradient(closestWind, secondClosestWind, altitude);
			} else {
				// No wind below exist. Return original closest wind
				return closestWind;
			}
		} else {
			// Closest wind is below balloon
			// Find closest wind above balloon
			distance = null;
			for (Wind currentWind : winds) {
				if (currentWind.getHeight() > altitude) {
					Double currentDistance = currentWind.getHeight() - altitude;
					if (distance == null || currentDistance < distance) {
						distance = currentDistance;
						secondClosestWind = currentWind;
					}
				}
			}

			if (secondClosestWind != null) {
				// Wind above found. Return wind gradient
				return getWindGradient(closestWind, secondClosestWind, altitude);
			} else {
				// No wind above exist. Return original closest wind
				return closestWind;
			}
		}
	}

	private void moveBallon(Balloon balloon) {
		// Get wind stack corresponding to location of given balloon
		Wind[] winds = css.getEnvironment().getWindArray(new Vector(balloon.getLatitude(), balloon.getLongitude()));

		// Get corresponding wind
		Wind wind = getWind(balloon.getAltitude(), winds);

		Long simulationInterval = css.getSimulation().getSimulationInterval();

		balloon.setLatitude(balloon.getLatitude() + wind.getVector().getX() * simulationInterval);
		balloon.setLongitude(balloon.getLongitude() + wind.getVector().getY() * simulationInterval);
	}

	@Override
	public void run() {
		for (Balloon b : css.getBalloons()) {
			moveBallon(b);
		}
	}
}
